package xyz.yang.persistence.mongo.core;

import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import xyz.yang.query.PageResult;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

/**
 * @author Xuehong Yang
 * @since 1.0
 */

public class MongoPageHelper {

    public static final int FIRST_PAGE_NUM = 1;
    public static final String ID = "_id";
    private final MongoTemplate mongoTemplate;

    public MongoPageHelper(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }


    /**
     * 分页查询，直接返回集合类型的结果.
     *
     * @see MongoPageHelper#pageQuery(Query,
     * Class, Function, Integer, Integer,
     * String)
     */
    public <T extends Serializable> PageResult<T> pageQuery(Query query, Class<T> entityClass, Integer pageSize,
                                                            Integer pageNum, String collectionName) {
        return pageQuery(query, entityClass, Function.identity(), pageSize, pageNum, null, collectionName);
    }

    /**
     * 分页查询，不考虑条件分页，直接使用skip-limit来分页.
     *
     * @see MongoPageHelper#pageQuery(Query,
     * Class, Function, Integer, Integer,
     * String)
     */
    @SuppressWarnings("unused")
    public <T, R extends Serializable> PageResult<R> pageQuery(Query query, Class<T> entityClass,
                                                               Function<T, R> mapper, Integer pageSize,
                                                               Integer pageNum, String collectionName) {
        return pageQuery(query, entityClass, mapper, pageSize, pageNum, null, collectionName);
    }

    /**
     * 分页查询.
     *
     * @param query       Mongo Query对象，构造你自己的查询条件.
     * @param entityClass Mongo collection定义的entity class，用来确定查询哪个集合.
     * @param mapper      映射器，你从db查出来的list的元素类型是entityClass, 如果你想要转换成另一个对象，比如去掉敏感字段等，可以使用mapper来决定如何转换.
     * @param pageSize    分页的大小.
     * @param pageNum     当前页.
     * @param lastId      条件分页参数, 区别于skip-limit，采用find(_id>lastId).limit分页.
     *                    如果不跳页，像朋友圈，微博这样下拉刷新的分页需求，需要传递上一页的最后一条记录的ObjectId。 如果是null，则返回pageNum那一页.
     * @param <T>         collection定义的class类型.
     * @param <R>         最终返回时，展现给页面时的一条记录的类型。
     * @return PageResult，一个封装page信息的对象.
     */
    public <T, R extends Serializable> PageResult<R> pageQuery(Query query, Class<T> entityClass,
                                                               Function<T, R> mapper, Integer pageSize,
                                                               Integer pageNum, String lastId, String collectionName) {
        //分页逻辑
        long total = mongoTemplate.count(query, entityClass, collectionName);
        final Integer pages = (int) Math.ceil(total / (double) pageSize);
        if (pageNum <= 0 || pageNum > pages) {
            return new PageResult<>(pageNum, pageSize, total, pages, new ArrayList<>());
        }
        final Criteria criteria = new Criteria();
        if (!Objects.isNull(lastId)) {
            if (pageNum != FIRST_PAGE_NUM) {
                criteria.and(ID).gt(new ObjectId(lastId));
                query.addCriteria(criteria);
            }
            query.limit(pageSize);
        } else {
            int skip = pageSize * (pageNum - 1);
            query.skip(skip).limit(pageSize);
        }

        final List<T> entityList = mongoTemplate.find(query, entityClass, collectionName);

        return new PageResult<>(pageNum, pageSize, total, pages, entityList.stream().map(mapper).toList());
    }
}